Micron Document
🎖️GitЯра🎖️


Displaying Raw • Download

core/takserver/src/commonMain/kotlin/org/meshtastic/core/takserver/MeshNodeCoTConversion.kt a31fa287e29db2a5713a771de2d1503eb2f5fcfe (a31fa287) Text, 5.20 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.takserver

Tff7b72import T7ee787org.meshtastic.core.common.util.NumberFormatter
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.core.model.util.onlineTimeThreshold

T8b949e// Converts entries of the Meshtastic node database into CoT contacts for connected TAK clients.
T8b949e//
T8b949e// This is the local mesh -> ATAK visualization path. Nothing produced here crosses the mesh, so none of it is subject
T8b949e// to the LoRa MTU or the TAKPacket wire format — that is TAKMeshIntegration's concern.

T8b949e/**
* True when [this] node should be presented to TAK clients as a contact.
*
* Mirrors Meshtastic-Apple's filter: the node must have identified itself, been heard recently, and hold a usable
* position. [ourNodeNum] is excluded because ATAK already renders the operator's own position as self; when it is null
* the local node number is not yet known and nothing is excluded.
*/
Tff7b72internal Tff7b72fun Te6edf3NodeTb4b4b4.Td2a8ffisEligibleForCotTb4b4b4(Te6edf3ourNodeNumTb4b4b4: Tffa657Int?Tb4b4b4, Te6edf3lastHeardThresholdTb4b4b4: Tffa657Int Tff7b72= Te6edf3onlineTimeThresholdTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72=
Te6edf3num Tff7b72!Tff7b72= Te6edf3ourNodeNum Tff7b72&Tff7b72& Tff7b72!Te6edf3isUnknownUser Tff7b72&Tff7b72& Te6edf3lastHeard Tff7b72> Te6edf3lastHeardThreshold Tff7b72&Tff7b72& Te6edf3validPosition Tff7b72!Tff7b72= Tff7b72null

T8b949e/**
* The stable CoT UID for [this] node, e.g. `MESHTASTIC-A1B2C3D4`.
*
* ATAK identifies a contact by UID, so this must stay stable across broadcasts or every refresh spawns a duplicate
* marker. Derived from the node number rather than the user record because [Node.user] can be empty or change.
*
* Upper-case hex is load-bearing, not cosmetic — see [MESH_NODE_UID_PREFIX]. Deliberately not built from
* [NodeAddress.numToDefaultId], which formats `!%08x` in lower case for display.
*/
Tff7b72internal Tff7b72fun Te6edf3NodeTb4b4b4.Td2a8ffcotUidTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String Tff7b72=
Te6edf3MESH_NODE_UID_PREFIX Tff7b72+ Te6edf3numTb4b4b4.Te6edf3toUIntTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toStringTb4b4b4(Te6edf3TAK_HEX_RADIXTb4b4b4)Tb4b4b4.Te6edf3uppercaseTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3padStartTb4b4b4(Te6edf3MESH_NODE_UID_HEX_WIDTHTb4b4b4, Ta5d6ff'0'Tb4b4b4)

T8b949e/**
* The ATAK callsign for [this] node — `"SHORT - Long Name"`, matching Apple. Falls back through the names that are
* actually populated so a partially-identified node never renders as a blank contact.
*/
Tff7b72internal Tff7b72fun Te6edf3NodeTb4b4b4.Td2a8ffcotCallsignTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String Tb4b4b4{
Tff7b72val Te6edf3short Tff7b72= Te6edf3userTb4b4b4.Te6edf3short_nameTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3long Tff7b72= Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72when Tb4b4b4{
Te6edf3shortTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72&Tff7b72& Te6edf3longTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Ta5d6ff"Tffd700$Te6edf3shortTa5d6ff - Tffd700$Te6edf3longTa5d6ff"
Te6edf3shortTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Te6edf3short
Te6edf3longTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Te6edf3long
Tff7b72else Tff7b72-Tff7b72> Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3numTb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Telemetry summary carried in the CoT `<remarks>` element.
*
* Field labels, order, and precision match Apple's `createCoTFromNode` so an operator reading a contact's remarks sees
* the same text regardless of which phone bridged it.
*
* **Deliberate divergence:** Apple suppresses a field when its value is zero (`if voltage > 0`, `if rssi != 0`, …).
* This repo treats zero as a real reading — 0 dB SNR and 0 dBm RSSI are genuine measurements, and 0% battery is exactly
* the value an operator most needs to see — so absence is detected via nullability and the SNR/RSSI sentinels
* ([Node.snrOrNull] / [Node.rssiOrNull]) instead. Apple also substitutes 100% for an unreported battery; omitting it is
* preferred over reporting a figure the node never sent. `Air Util Tx` has no Apple counterpart and is additive.
*/
Tff7b72internal Tff7b72fun Te6edf3NodeTb4b4b4.Td2a8ffcotRemarksTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657String? Tb4b4b4{
Tff7b72val Te6edf3parts Tff7b72= Te6edf3buildList Tb4b4b4{
Te6edf3batteryLevelTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3addTb4b4b4(Ta5d6ff"Ta5d6ffBattery: Tffd700$Te6edf3itTa5d6ff%Ta5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3voltageTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3addTb4b4b4(Ta5d6ff"Ta5d6ffVoltage: Tffd700${Te6edf3NumberFormatterTb4b4b4.Te6edf3formatTb4b4b4(Tffa657itTb4b4b4, Te6edf3VOLTAGE_DECIMALSTb4b4b4)Tffd700}Ta5d6ffVTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3deviceMetricsTb4b4b4.Te6edf3channel_utilizationTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3addTb4b4b4(Ta5d6ff"Ta5d6ffChan Util: Tffd700${Te6edf3NumberFormatterTb4b4b4.Te6edf3formatTb4b4b4(Tffa657itTb4b4b4, T79c0ff1Tb4b4b4)Tffd700}Ta5d6ff%Ta5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3deviceMetricsTb4b4b4.Te6edf3air_util_txTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3addTb4b4b4(Ta5d6ff"Ta5d6ffAir Util Tx: Tffd700${Te6edf3NumberFormatterTb4b4b4.Te6edf3formatTb4b4b4(Tffa657itTb4b4b4, T79c0ff1Tb4b4b4)Tffd700}Ta5d6ff%Ta5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3rssiOrNullTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3addTb4b4b4(Ta5d6ff"Ta5d6ffRSSI: Tffd700$Te6edf3itTa5d6ff dBmTa5d6ff"Tb4b4b4) Tb4b4b4}
Te6edf3snrOrNullTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3addTb4b4b4(Ta5d6ff"Ta5d6ffSNR: Tffd700${Te6edf3NumberFormatterTb4b4b4.Te6edf3formatTb4b4b4(Tffa657itTb4b4b4, T79c0ff1Tb4b4b4)Tffd700}Ta5d6ff dBTa5d6ff"Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3partsTb4b4b4.Te6edf3joinToStringTb4b4b4(Ta5d6ff"Ta5d6ff | Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3ifEmpty Tb4b4b4{ Tff7b72null Tb4b4b4}
Tb4b4b4}

T8b949e/** Apple prints voltage with two decimals (`%.2f`) where every other telemetry field uses one. */
Tff7b72private Tff7b72const Tff7b72val Te6edf3VOLTAGE_DECIMALS Tff7b72= T79c0ff2

T8b949e/**
* Build the CoT event representing [this] node.
*
* Callers are expected to have filtered with [isEligibleForCot] first; a node without a valid position still converts,
* but yields a 0/0 point that ATAK will place in the Gulf of Guinea.
*/
Tff7b72internal Tff7b72fun Te6edf3NodeTb4b4b4.Td2a8fftoCoTMessageTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3CoTMessage Tff7b72= Te6edf3userTb4b4b4.Te6edf3toCoTMessageTb4b4b4(
Te6edf3position Tff7b72= Te6edf3validPositionTb4b4b4,
Te6edf3team Tff7b72= Te6edf3MESH_NODE_TAK_TEAMTb4b4b4,
Te6edf3role Tff7b72= Te6edf3DEFAULT_TAK_ROLE_NAMETb4b4b4,
Te6edf3battery Tff7b72= Te6edf3batteryLevel Tff7b72?: Te6edf3DEFAULT_TAK_BATTERYTb4b4b4,
Te6edf3uid Tff7b72= Te6edf3cotUidTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3callsign Tff7b72= Te6edf3cotCallsignTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3staleMinutes Tff7b72= Te6edf3MESH_NODE_STALE_MINUTESTb4b4b4,
Te6edf3remarks Tff7b72= Te6edf3cotRemarksTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Served by rngit 1.5.0 - Generated in 0.05s